package com.wmyg.tracker.sdk;

import org.apache.commons.codec.binary.Hex;
import org.hyperledger.fabric.protos.ledger.rwset.kvrwset.KvRwset;
import org.hyperledger.fabric.sdk.*;
import org.hyperledger.fabric.sdk.exception.*;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;

import static java.lang.String.format;
import static org.hyperledger.fabric.sdk.BlockInfo.EnvelopeType.TRANSACTION_ENVELOPE;

/**
 * 描述：区块链网络服务管理器
 *
 * @author : Aberic 【2018/5/4 16:43】
 */
public class FabricManager {

    private IntermediateOrg org;


    private HashMap<String, IntermediateChannel> channels;

    private  IntermediateChannel interChannle;

    final public  static HFClient hfClient = HFClient.createNewInstance();

    FabricManager(IntermediateOrg org) {
        this.org = org;
        this.channels = new HashMap<>();
    }

    /**
     * 安装智能合约
     */
    public Map<String, String> install() throws ProposalException, InvalidArgumentException, TransactionException {
        return org.getChainCode().install(org);
    }

    /**
     * 实例化智能合约
     *
     * @param args 初始化参数数组
     */
    public Map<String, String> instantiate(String[] args) throws ProposalException, InvalidArgumentException, IOException, ChaincodeEndorsementPolicyParseException, InterruptedException, ExecutionException, TimeoutException, TransactionException {
        return org.getChainCode().instantiate(org, args);
    }

    /**
     * 升级智能合约
     *
     * @param args 初始化参数数组
     */
    public Map<String, String> upgrade(String[] args) throws ProposalException, InvalidArgumentException, IOException, ChaincodeEndorsementPolicyParseException, InterruptedException, ExecutionException, TimeoutException, TransactionException {
        return org.getChainCode().upgrade(org, args);
    }

    /**
     * 执行智能合约
     *
     * @param fcn  方法名
     * @param args 参数数组
     */
//    public Map<String, String> invoke(String fcn, String[] args) throws InvalidArgumentException, ProposalException, IOException, InterruptedException, ExecutionException, TimeoutException, TransactionException {
//       // return org.getChainCode().invoke(org, fcn, args);
//        return org.getChainCode().invoke(interChannle, hfClient,fcn, args);
//    }


    public FabricResult invoke(String fcn, String[] args)  {
       // return org.getChainCode().invoke(org, fcn, args);
        //return org.getChainCode().invoke(interChannle, hfClient,fcn, args);

        FabricResult fabricResult = new FabricResult();
        try {
            Map<String, String> map = org.getChainCode().invoke(interChannle, hfClient,fcn, args);
            if ("error".equals(map.get("code"))) {
                fabricResult.setCode(4);
                fabricResult.setData(map.get("data"));
                fabricResult.setDescription(map.get("cause"));
                return fabricResult;
            }
            fabricResult.setCode(0);
            fabricResult.setDescription("success");
            fabricResult.setData(map.get("data"));
        } catch (InvalidArgumentException e) {
            /*
             * 无效参数错误
             * 1. 提案请求关键字为空
             * 2. 提案请求内容为空
             * 3. 提案请求方法名为空
             * 4. 提案请求链码ID为空
             * 5. 提案请求已被提交
             * 6. 用户上下文错误包括整个用户上下文、用户名、mspID、是否登记等
             * 7. 提案请求链码类型错误
             * 8. 提案响应集各种错误
             * 9. 通道未初始化或者已关闭
             * 10. 未设置目标节点
             */
            e.printStackTrace();
            fabricResult.setCode(1);
            fabricResult.setDescription(e.getMessage());
//            logger.info("Find the Channel is shutdown, ready to restart program for the port " + serverPort);
//            try {
//                Runtime.getRuntime().exec("nohup /mnt/fabricuploader/fabricuploader.sh " + serverPort + " &");
//            } catch (IOException e1) {
//                e1.printStackTrace();
//            }
//            fabricManager = orgManager.useAgain(orgName.hashCode());
//            throw e;
        } catch (ProposalException e) {
            /*
             * 提案异常
             * 1. 提案无法进行签名
             * 2. 提案请求建立异常
             */
            e.printStackTrace();
            fabricResult.setCode(2);
            fabricResult.setDescription(e.getMessage());
        } catch (TimeoutException e) {
            //各种超时，包括peer请求超时，orderer请求超时
            e.printStackTrace();
            fabricResult.setCode(3);
            fabricResult.setDescription(e.getMessage());
//        } catch (FabricException e) {
//            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
            fabricResult.setCode(5);
            fabricResult.setDescription(e.getMessage());
        } catch (InterruptedException e) {
            e.printStackTrace();
            fabricResult.setCode(6);
            fabricResult.setDescription(e.getMessage());
        } catch (IOException e) {
            //字符集转换错误
            e.printStackTrace();
            fabricResult.setCode(7);
            fabricResult.setDescription(e.getMessage());
        } catch (TransactionException e) {
            e.printStackTrace();
            fabricResult.setCode(7);
            fabricResult.setDescription(e.getMessage());
        }
        return fabricResult;
    }

//    /**
//     * 查询智能合约
//     *
//     * @param fcn  方法名
//     * @param args 参数数组
//     */
//    public Map<String, String> query(String fcn, String[] args) throws InvalidArgumentException, ProposalException, TransactionException {
//        //return org.getChainCode().query(org, fcn, args);
//        FabricResult fabricResult = new FabricResult();
//        return org.getChainCode().query(interChannle, hfClient, fcn, args);
//    }

    /**
     * 查询智能合约
     *
     * @param fcn  方法名
     * @param args 参数数组
     */
    public FabricResult query(String fcn, String[] args)  {
        //return org.getChainCode().query(org, fcn, args);
        FabricResult fabricResult = new FabricResult();
        try {
            Map<String, String> map = org.getChainCode().query(interChannle, hfClient, fcn, args);
//        System.out.println(map);
            if ("error".equals(map.get("code"))) {
//                fabricManager.abandon();
//                throw new FabricException(map.get("cause"));
                fabricResult.setCode(4);
                fabricResult.setData(map.get("data"));
                fabricResult.setDescription(map.get("cause"));
                return fabricResult;
            }

            fabricResult.setCode(0);
            fabricResult.setDescription("success");
            fabricResult.setData(map.get("data"));
        } catch (InvalidArgumentException e) {
            /*
             * 无效参数错误
             * 1. 提案请求关键字为空
             * 2. 提案请求内容为空
             * 3. 提案请求方法名为空
             * 4. 提案请求链码ID为空
             * 5. 提案请求已被提交
             * 6. 用户上下文错误包括整个用户上下文、用户名、mspID、是否登记等
             * 7. 提案请求链码类型错误
             * 8. 提案响应集各种错误
             * 9. 通道未初始化或者已关闭
             * 10. 未设置目标节点
             */
            e.printStackTrace();
            fabricResult.setCode(1);
            fabricResult.setDescription(e.getMessage());
//            logger.info("Find the Channel is shutdown, ready to restart program for the port " + serverPort);
//            try {
//                Runtime.getRuntime().exec("nohup /mnt/fabricuploader/fabricuploader.sh " + serverPort + " &");
//            } catch (IOException e1) {
//                e1.printStackTrace();
//            }
//            fabricManager = orgManager.useAgain(orgName.hashCode());
//            throw e;
        } catch (ProposalException e) {
            /*
             * 提案异常
             * 1. 提案无法进行签名
             * 2. 提案请求建立异常
             */
            e.printStackTrace();
            fabricResult.setCode(2);
            fabricResult.setDescription(e.getMessage());
        }
//        catch (TimeoutException e) {
//            //各种超时，包括peer请求超时，orderer请求超时
//            e.printStackTrace();
//            fabricResult.setCode(3);
//            fabricResult.setDescription(e.getMessage());
////        } catch (FabricException e) {
////            e.printStackTrace();
//        } catch (ExecutionException e) {
//            e.printStackTrace();
//            fabricResult.setCode(5);
//            fabricResult.setDescription(e.getMessage());
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//            fabricResult.setCode(6);
//            fabricResult.setDescription(e.getMessage());
//        } catch (IOException e) {
//            //字符集转换错误
//            e.printStackTrace();
//            fabricResult.setCode(7);
//            fabricResult.setDescription(e.getMessage());
//        }
       catch (TransactionException e) {
            e.printStackTrace();
            fabricResult.setCode(7);
            fabricResult.setDescription(e.getMessage());
        }
        return fabricResult;

    }

    /*
         The caller will hold the ownership of the return IntermediateChannel and
         should handle the life of it.
       *
        */
    public IntermediateChannel createChannel(String alias){
        try {

            //  Channel channel = client.newChannel(channelName, Thread.currentThread().getName());
            //  channels.put(Thread.currentThread().getName(), channel);
            //??????
            //if (!channels.containsKey(alias)) {
            if(interChannle==null) {
                interChannle = new IntermediateChannel();

                //channel.setChannelName(channelName);
                //orgMap.get(id).setChannel(channel);

                interChannle.init(org);

                //org.setChannel(channel); ///??????
                channels.put(alias, interChannle);
            }
            return interChannle;
//            }
//            else{
//                return null;
//            }
        }
        catch (TransactionException e){
            return null;
        }
        catch (InvalidArgumentException e){
            return null;
        }

    }


    /**
     * 在指定频道内根据transactionID查询区块
     *
     * @param txID transactionID
     */
    public Map<String, String> queryBlockByTransactionID(String txID) throws ProposalException, IOException, InvalidArgumentException, TransactionException {
        return org.getChannel().queryBlockByTransactionID(txID);
    }

    /**
     * 在指定频道内根据hash查询区块
     *
     * @param blockHash hash
     */
    public Map<String, String> queryBlockByHash(byte[] blockHash) throws ProposalException, IOException, InvalidArgumentException, TransactionException {
        return org.getChannel().queryBlockByHash(blockHash);
    }

    /**
     * 在指定频道内根据区块高度查询区块
     *
     * @param blockNumber 区块高度
     */
    public Map<String, String> queryBlockByNumber(long blockNumber) throws ProposalException, IOException, InvalidArgumentException, TransactionException {
        return org.getChannel().queryBlockByNumber(blockNumber);
    }

    /**
     * Peer加入频道
     *
     * @param peerName             当前指定的组织节点域名
     * @param peerEventHubName     当前指定的组织节点事件域名
     * @param peerLocation         当前指定的组织节点访问地址
     * @param peerEventHubLocation 当前指定的组织节点事件监听访问地址
     * @param isEventListener      当前peer是否增加Event事件处理
     */
    public Map<String, String> joinPeer(String peerName, String peerEventHubName, String peerLocation, String peerEventHubLocation, boolean isEventListener) throws ProposalException, InvalidArgumentException, TransactionException {
        return org.getChannel().joinPeer(new IntermediatePeer(peerName, peerEventHubName, peerLocation, peerEventHubLocation, isEventListener));
    }

    /**
     * 查询当前频道的链信息，包括链长度、当前最新区块hash以及当前最新区块的上一区块hash
     */
    public Map<String, String> getBlockchainInfo() throws ProposalException, InvalidArgumentException, TransactionException {
        return org.getChannel().getBlockchainInfo();
    }

    public void abandon() {
//        org.getChannel().close();
//        org.getClient().
        try {
            if (interChannle != null) {
                interChannle.close();
            }
            this.finalize();
        }
        catch (Throwable t){
            t.printStackTrace();

        }
    }

    public void checkChannelState() throws TransactionException, InvalidArgumentException {
        if (!org.getChannel().get().isInitialized()) {
            org.getChannel().get().initialize();
        }
    }

    public void bwTest() throws ProposalException, IOException, InvalidArgumentException, TransactionException {
        blockWalker(org.getClient(), org.getChannel().get());
    }

    void blockWalker(HFClient client, Channel channel) throws InvalidArgumentException, ProposalException, IOException {
        try {
            BlockchainInfo channelInfo = channel.queryBlockchainInfo();

            long a = channelInfo.getHeight();

            for (long current = channelInfo.getHeight() - 1; current > -1; --current) {
                BlockInfo returnedBlock = channel.queryBlockByNumber(current);
                final long blockNumber = returnedBlock.getBlockNumber();

                out("current block number %d has data hash: %s", blockNumber, Hex.encodeHexString(returnedBlock.getDataHash()));
                out("current block number %d has previous hash id: %s", blockNumber, Hex.encodeHexString(returnedBlock.getPreviousHash()));
                out("current block number %d has calculated block hash is %s", blockNumber, Hex.encodeHexString(SDKUtils.calculateBlockHash(client,
                        blockNumber, returnedBlock.getPreviousHash(), returnedBlock.getDataHash())));

                final int envelopeCount = returnedBlock.getEnvelopeCount();
//                assertEquals(1, envelopeCount);
                out("current block number %d has %d envelope count:", blockNumber, returnedBlock.getEnvelopeCount());
                int i = 0;
                int transactionCount = 0;
                for (BlockInfo.EnvelopeInfo envelopeInfo : returnedBlock.getEnvelopeInfos()) {
                    ++i;

                    out("  Transaction number %d has transaction id: %s", i, envelopeInfo.getTransactionID());
                    final String channelId = envelopeInfo.getChannelId();
//                    assertTrue("foo".equals(channelId) || "bar".equals(channelId));

                    out("  Transaction number %d has channel id: %s", i, channelId);
                    out("  Transaction number %d has epoch: %d", i, envelopeInfo.getEpoch());
                    out("  Transaction number %d has transaction timestamp: %tB %<te,  %<tY  %<tT %<Tp", i, envelopeInfo.getTimestamp());
                    out("  Transaction number %d has type id: %s", i, "" + envelopeInfo.getType());
                    out("  Transaction number %d has nonce : %s", i, "" + Hex.encodeHexString(envelopeInfo.getNonce()));
                    out("  Transaction number %d has submitter mspid: %s,  certificate: %s", i, envelopeInfo.getCreator().getMspid(), envelopeInfo.getCreator().getId());

                    if (envelopeInfo.getType() == TRANSACTION_ENVELOPE) {
                        ++transactionCount;
                        BlockInfo.TransactionEnvelopeInfo transactionEnvelopeInfo = (BlockInfo.TransactionEnvelopeInfo) envelopeInfo;

                        out("  Transaction number %d has %d actions", i, transactionEnvelopeInfo.getTransactionActionInfoCount());
//                        assertEquals(1, transactionEnvelopeInfo.getTransactionActionInfoCount()); // for now there is only 1 action per transaction.
                        out("  Transaction number %d isValid %b", i, transactionEnvelopeInfo.isValid());
//                        assertEquals(transactionEnvelopeInfo.isValid(), true);
                        out("  Transaction number %d validation code %d", i, transactionEnvelopeInfo.getValidationCode());
//                        assertEquals(0, transactionEnvelopeInfo.getValidationCode());

                        int j = 0;
                        for (BlockInfo.TransactionEnvelopeInfo.TransactionActionInfo transactionActionInfo : transactionEnvelopeInfo.getTransactionActionInfos()) {
                            ++j;
                            out("   Transaction action %d has response status %d", j, transactionActionInfo.getResponseStatus());
//                            assertEquals(200, transactionActionInfo.getResponseStatus());
                            out("   Transaction action %d has response message bytes as string: %s", j,
                                    printableString(new String(transactionActionInfo.getResponseMessageBytes(), "UTF-8")));
                            out("   Transaction action %d has %d endorsements", j, transactionActionInfo.getEndorsementsCount());
//                            assertEquals(2, transactionActionInfo.getEndorsementsCount());

                            for (int n = 0; n < transactionActionInfo.getEndorsementsCount(); ++n) {
                                BlockInfo.EndorserInfo endorserInfo = transactionActionInfo.getEndorsementInfo(n);
                                out("Endorser %d signature: %s", n, Hex.encodeHexString(endorserInfo.getSignature()));
                                out("Endorser %d endorser: mspid %s \n certificate %s", n, endorserInfo.getMspid(), endorserInfo.getId());
                            }
                            out("   Transaction action %d has %d chaincode input arguments", j, transactionActionInfo.getChaincodeInputArgsCount());
                            for (int z = 0; z < transactionActionInfo.getChaincodeInputArgsCount(); ++z) {
                                out("     Transaction action %d has chaincode input argument %d is: %s", j, z,
                                        printableString(new String(transactionActionInfo.getChaincodeInputArgs(z), "UTF-8")));
                            }

                            out("   Transaction action %d proposal response status: %d", j,
                                    transactionActionInfo.getProposalResponseStatus());
                            out("   Transaction action %d proposal response payload: %s", j,
                                    printableString(new String(transactionActionInfo.getProposalResponsePayload())));

                            // Check to see if we have our expected event.
                            if (blockNumber == 2) {
                                ChaincodeEvent chaincodeEvent = transactionActionInfo.getEvent();
//                                assertNotNull(chaincodeEvent);

//                                assertTrue(Arrays.equals(EXPECTED_EVENT_DATA, chaincodeEvent.getPayload()));
//                                assertEquals(testTxID, chaincodeEvent.getTxId());
//                                assertEquals(CHAIN_CODE_NAME, chaincodeEvent.getChaincodeId());
//                                assertEquals(EXPECTED_EVENT_NAME, chaincodeEvent.getEventName());

                            }

                            TxReadWriteSetInfo rwsetInfo = transactionActionInfo.getTxReadWriteSet();
                            if (null != rwsetInfo) {
                                out("   Transaction action %d has %d name space read write sets", j, rwsetInfo.getNsRwsetCount());

                                for (TxReadWriteSetInfo.NsRwsetInfo nsRwsetInfo : rwsetInfo.getNsRwsetInfos()) {
                                    final String namespace = nsRwsetInfo.getNamespace();
                                    KvRwset.KVRWSet rws = nsRwsetInfo.getRwset();

                                    int rs = -1;
                                    for (KvRwset.KVRead readList : rws.getReadsList()) {
                                        rs++;

                                        out("     Namespace %s read set %d key %s  version [%d:%d]", namespace, rs, readList.getKey(),
                                                readList.getVersion().getBlockNum(), readList.getVersion().getTxNum());

                                        if ("bar".equals(channelId) && blockNumber == 2) {
                                            if ("example_cc_go".equals(namespace)) {
                                                if (rs == 0) {
//                                                    assertEquals("a", readList.getKey());
//                                                    assertEquals(1, readList.getVersion().getBlockNum());
//                                                    assertEquals(0, readList.getVersion().getTxNum());
                                                } else if (rs == 1) {
//                                                    assertEquals("b", readList.getKey());
//                                                    assertEquals(1, readList.getVersion().getBlockNum());
//                                                    assertEquals(0, readList.getVersion().getTxNum());
                                                } else {
//                                                    fail(format("unexpected readset %d", rs));
                                                }

                                                TX_EXPECTED.remove("readset1");
                                            }
                                        }
                                    }

                                    rs = -1;
                                    for (KvRwset.KVWrite writeList : rws.getWritesList()) {
                                        rs++;
                                        String valAsString = printableString(new String(writeList.getValue().toByteArray(), "UTF-8"));

                                        out("     Namespace %s write set %d key %s has value '%s' ", namespace, rs,
                                                writeList.getKey(),
                                                valAsString);

                                        if ("bar".equals(channelId) && blockNumber == 2) {
                                            if (rs == 0) {
//                                                assertEquals("a", writeList.getKey());
//                                                assertEquals("400", valAsString);
                                            } else if (rs == 1) {
//                                                assertEquals("b", writeList.getKey());
//                                                assertEquals("400", valAsString);
                                            } else {
//                                                fail(format("unexpected writeset %d", rs));
                                            }

                                            TX_EXPECTED.remove("writeset1");
                                        }
                                    }
                                }
                            }
                        }
                    }

//                    assertEquals(transactionCount, returnedBlock.getTransactionCount());

                }
            }
            if (!TX_EXPECTED.isEmpty()) {
//                fail(TX_EXPECTED.get(0));
            }
        } catch (InvalidProtocolBufferRuntimeException e) {
            throw e.getCause();
        }
    }

    static void out(String format, Object... args) {

        System.err.flush();
        System.out.flush();

        System.out.println(format(format, args));
        System.err.flush();
        System.out.flush();

    }

    static String printableString(final String string) {
        int maxLogStringLength = 64;
        if (string == null || string.length() == 0) {
            return string;
        }

        String ret = string.replaceAll("[^\\p{Print}]", "?");

        ret = ret.substring(0, Math.min(ret.length(), maxLogStringLength)) + (ret.length() > maxLogStringLength ? "..." : "");

        return ret;

    }



    int getInvokeWaitTime() {
        return org.getChainCode().getTransactionWaitTime();
    }

    void setInvokeWaitTime(int invokeWaitTime) {
        org.getChainCode().setTransactionWaitTime(invokeWaitTime);
    }

    private static Map<String, String> TX_EXPECTED;

}
